package com.yungee.lock;

import com.yungee.lock.handler.LockRejectHandler;
import com.yungee.util.SpringContextUtil;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

public class RedisLock implements AbstractLock {
    public static final ThreadLocal<RedisLock> LOCK_THREAD_LOCAL = new ThreadLocal();
    private static final String DEFAULT_VALUE = "1";
    private static final String NX = "NX";
    private static final String EX = "EX";
    private String name;
    private long expire;
    private int errorno;
    private String msg;
    private LockRejectHandler handler;
    private LockType type;
    private boolean isMem = false;

    public RedisLock(String name, long expire, int errorno, String msg, RejectedPolicy policy, LockType type) {
        this.name = name;
        this.expire = expire;
        this.errorno = errorno;
        this.msg = msg;
        this.handler = policy.getHandler();
        this.type = type;
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lockInterruptibly(3L, TimeUnit.SECONDS);
    }

    public void lockInterruptibly(long waitTime, TimeUnit unit) throws InterruptedException {
        StringRedisTemplate client = SpringContextUtil.getBean(StringRedisTemplate.class);
        Objects.requireNonNull(client);
        long timeWait = System.currentTimeMillis() + unit.toMillis(waitTime);

        while(System.currentTimeMillis() < timeWait) {
            Boolean result = client.opsForValue().setIfAbsent(this.name, DEFAULT_VALUE, Duration.ofSeconds(this.expire));
            if (Boolean.TRUE.equals(result)) {
                return;
            }
            Thread.sleep(RandomUtils.nextLong(0L, 100L));
        }

    }

    public void lock() {
        try {
            this.lockInterruptibly();
        } catch (InterruptedException var2) {
            Thread.currentThread().interrupt();
        }

    }

    public boolean tryLock() {
        StringRedisTemplate client = SpringContextUtil.getBean(StringRedisTemplate.class);
        Objects.requireNonNull(client);

        try {
            Boolean result = client.opsForValue().setIfAbsent(this.name, DEFAULT_VALUE, Duration.ofSeconds(this.expire));
            return Boolean.TRUE.equals(result) ;
        } catch (Exception var3) {
            throw var3;
        }
    }

    public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
        StringRedisTemplate client = SpringContextUtil.getBean(StringRedisTemplate.class);
        Objects.requireNonNull(client);
        long timeWait = System.currentTimeMillis() + unit.toMillis(waitTime);

        while(System.currentTimeMillis() < timeWait) {
            Boolean result = client.opsForValue().setIfAbsent(this.name, "1", Duration.ofSeconds(this.expire));
            if (Boolean.TRUE.equals(result)) {
                return true;
            }
            Thread.sleep(RandomUtils.nextLong(0L, 100L));
        }

        return false;
    }

    public boolean isLocked() {
        StringRedisTemplate client = SpringContextUtil.getBean(StringRedisTemplate.class);
        Objects.requireNonNull(client);
        return client.hasKey(this.name);
    }

    public void unlock() {
        StringRedisTemplate client = SpringContextUtil.getBean(StringRedisTemplate.class);
        Objects.requireNonNull(client);
        client.delete(this.name);
    }

    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    public String getName() {
        return this.name;
    }

    public long getExpire() {
        return this.expire;
    }

    public int getErrorno() {
        return this.errorno;
    }

    public String getMsg() {
        return this.msg;
    }

    public LockRejectHandler getHandler() {
        return this.handler;
    }

    public LockType getType() {
        return this.type;
    }

    public boolean isMem() {
        return this.isMem;
    }

    public void setMem(boolean isMem) {
        this.isMem = isMem;
    }
}
